<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>7.初步完成</title>
    <script>
        (function () {
            function CPU() {
                this.pc = 0x200;//CHIP8的程序都是从0x200地址开始的
                this.stack = new Array;//堆栈指针
                this.screen = { clear: function () { }, render: function () { }, setPixel: function () { } };//显示
                this.input = { isKeyPressed: function (key) { }, clear: function () { } };//输入
                this.speaker = { clear: function () { }, play: function () { }, stop: function () { } };//扬声器
                this.v = new Uint8Array(16);//16个数据寄存器 V0~VF
                this.i = 0;//地址寄存器
                this.memory = new Uint8Array(4096);//4K内存
                this.delayTimer = 0;//延时计时器
                this.soundTimer = 0;//声音计时器
                this.paused = false;//暂停
                this.speed = 10;//运行速度

                /**
                 * 用默认值重置CPU的一些参数
                 */
                this.reset = function () {
                    this.pc = 0x200;
                    this.stack = new Array;
                    this.v = new Uint8Array(16);
                    this.i = 0;
                    this.memory = new Uint8Array(4096);
                    this.delayTimer = 0;
                    this.soundTimer = 0;
                    this.screen.clear();
                    this.input.clear();
                    this.speaker.clear();
                    this.loadFonts();
                    this.paused = false;
                };

                /**
                 * 显示渲染
                 */
                this.render = function () { this.screen.render(); };

                /**
                 * 播放扬声器直到声音计时器达到零 
                 */
                this.playSound = function () {
                    if (this.soundTimer > 0) {//只要soundTimer的值大于零，CHIP8蜂鸣器发声。
                        this.speaker.play();
                    } else {
                        this.speaker.stop();
                    }
                }

                /**
                 * 更新CPU延迟和声音计时器 
                 */
                this.updateTimers = function () {
                    if (this.delayTimer > 0) this.delayTimer -= 1;//递减至0
                    if (this.soundTimer > 0) this.soundTimer -= 1;//递减至0
                }

                /**
                 * 加载字体到chip8内存
                 */
                this.loadFonts = function () {
                    var fonts = [
                        0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
                        0x20, 0x60, 0x20, 0x20, 0x70, // 1
                        0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
                        0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
                        0x90, 0x90, 0xF0, 0x10, 0x10, // 4
                        0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
                        0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
                        0xF0, 0x10, 0x20, 0x40, 0x40, // 7
                        0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
                        0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
                        0xF0, 0x90, 0xF0, 0x90, 0x90, // A
                        0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
                        0xF0, 0x80, 0x80, 0x80, 0xF0, // C
                        0xE0, 0x90, 0x90, 0x90, 0xE0, // D
                        0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
                        0xF0, 0x80, 0xF0, 0x80, 0x80  // F
                    ];
                    for (var i = 0, length = fonts.length; i < length; i++) {
                        this.memory[i] = fonts[i];
                    }
                };


                /**
                * 装程序到内存 
                * @param {Array} program  二进制程序
                */
                this.loadProgram = function (program) {
                    for (var i = 0, length = program.length; i < length; i++) {
                        this.memory[0x200 + i] = program[i];
                    }
                }

                /**
                 * CPU 开始执行
                 */
                this.cycle = function () {
                    for (var i = 0; i < this.speed; i++) {
                        if (!this.paused) {
                            var opcode = this.memory[this.pc] << 8 | this.memory[this.pc + 1]; //获取操作码，chip-8操作码是两个字节的长度，我们可以读到这两个字节或连接起来
                            this.perform(opcode);
                        }
                    }
                    if (!this.paused) {
                        this.updateTimers();
                    }
                    this.playSound();
                    this.render();
                };

                /**
                 * 一个给定的操作码的进行解析执行
                 * @param {Integer} opcode
                 */
                this.perform = function (opcode) {
                    this.pc += 2;//每个指令都是两个字节长 
                    var x = (opcode & 0x0F00) >> 8;
                    var y = (opcode & 0x00F0) >> 4;//取得x,y 
                    var NNN = opcode & 0x0FFF;
                    var NN = opcode & 0x00FF;
                    var self = this;
                    ({
                        0x0000() {
                            let r = ({
                                //00E0
                                //执行“清理屏幕”
                                0x00E0() {
                                    self.screen.clear();
                                },
                                //00EE
                                //执行“从子函数返回”
                                0x00EE() {
                                    self.pc = self.stack.pop();
                                }
                            })[opcode];
                            if (r) r();
                        },
                        //1NNN
                        //跳转到地址:NNN
                        //例如：0x1222 则跳转到 0x0222
                        0x1000() {
                            self.pc = NNN;
                        },
                        //2NNN
                        //解释器递增堆栈指针,然后跳转到地址:NNN
                        0x2000() {
                            self.stack.push(self.pc);
                            self.pc = NNN;
                        },
                        //3XNN
                        // if(Vx==NN) 将程序计数器递增2 跳过
                        0x3000() {
                            if (self.v[x] == NN) self.pc += 2;
                        },
                        //4XNN
                        //if(Vx!=NN)  将程序计数器递增2 跳过
                        0x4000() {
                            if (self.v[x] != NN) self.pc += 2;
                        },
                        //5XY0
                        //if(Vx==Vy) 将程序计数器递增2 跳过
                        0x5000() {
                            if (self.v[x] == self.v[y]) self.pc += 2;
                        },
                        //6XNN
                        //设置 Vx=NN
                        0x6000() {
                            self.v[x] = NN;
                        },
                        //7XNN
                        //设置 Vx+=NN
                        0x7000() {
                            self.v[x] += NN;
                        },
                        //8XY0
                        0x8000() {
                            ({
                                //8XY0
                                //Vx=Vy
                                0x0000() {
                                    self.v[x] = self.v[y];
                                },
                                //8XY1
                                //设置 Vx=Vx|Vy
                                0x0001() {
                                    self.v[x] = self.v[x] | self.v[y];
                                },
                                //8XY2
                                //Vx=Vx&Vy
                                0x0002() {
                                    self.v[x] = self.v[x] & self.v[y];
                                },
                                //8XY3
                                //Vx=Vx^Vy
                                0x0003() {
                                    self.v[x] = self.v[x] ^ self.v[y];
                                },
                                //8XY4
                                //Vx += Vy
                                0x0004() {
                                    var sum = self.v[x] + self.v[y];
                                    if (sum > 0xFF) {//即VY+VX > 255 
                                        self.v[0xF] = 1;//出现了溢出，则把VF置为1
                                    } else {
                                        self.v[0xF] = 0;//没有溢出VF置为0
                                    }
                                    self.v[x] = sum;
                                },
                                //8XY5
                                //Vx -= Vy
                                0x0005() {
                                    if (self.v[x] > self.v[y]) {
                                        self.v[0xF] = 1;
                                    } else {
                                        self.v[0xF] = 0;
                                    }
                                    self.v[x] = self.v[x] - self.v[y];
                                },
                                //8XY6
                                //Vx=Vy=Vy>>1
                                0x0006() {
                                    self.v[0xF] = self.v[x] & 0x01;
                                    self.v[x] = self.v[x] >> 1;
                                },
                                //8XY7
                                //Vx=Vy-Vx
                                0x0007() {
                                    if (self.v[x] > self.v[y]) {
                                        this.v[0xF] = 0;
                                    } else {
                                        self.v[0xF] = 1;
                                    }
                                    self.v[x] = self.v[y] - self.v[x];
                                },
                                //8XYE
                                //Vx=Vy=Vy<<1
                                0x000E() {
                                    self.v[0xF] = self.v[x] & 0x80;
                                    self.v[x] = self.v[x] << 1;
                                }
                            })[opcode & 0x000F]();
                        },
                        //if(Vx!=Vy)  将程序计数器递增2 跳过
                        0x9000() {
                            if (self.v[x] != self.v[y]) self.pc += 2;
                        },
                        //ANNN
                        //设置 I = NNN
                        0xA000() {
                            self.i = NNN;
                        },
                        //BNNN
                        //跳转到的位置NNN + V0
                        0xB000() {
                            self.pc = NNN + self.v[0];
                        },
                        //CXNN
                        //Vx=(随机0至255)&NN
                        0xC000() {
                            self.v[x] = Math.floor(Math.random() * 0xFF) & NN;
                        },
                        //DXYN
                        //绘画指令
                        0xD000() {
                            var row, col, sprite
                                , width = 8
                                , height = opcode & 0x000F;//取得N（图案的高度）

                            self.v[0xF] = 0;//初始化VF为0

                            for (row = 0; row < height; row++) {//对于每一行
                                sprite = self.memory[self.i + row];//取得内存I处的值，pixel中包含了一行的8个像素

                                for (col = 0; col < width; col++) {//对于一行的8个像素 
                                    if ((sprite & 0x80) > 0) {//依次检查新值中每一位是否为1 
                                        if (self.screen.setPixel(self.v[x] + col, self.v[y] + row)) {//如果显示缓存gfx[]里该像素也为1，则发生了碰撞
                                            self.v[0xF] = 1;//设置VF为1  
                                        }
                                    }
                                    sprite = sprite << 1;
                                }
                            }
                        },
                        0xE000() {
                            ({
                                //EX9E
                                //if(key()==Vx)  将程序计数器递增2 跳过
                                0x009E() {
                                    if (self.input.isKeyPressed(self.v[x])) self.pc += 2;
                                },
                                //EXA1
                                //if(key()!=Vx)  将程序计数器递增2 跳过
                                0x00A1() {
                                    if (!self.input.isKeyPressed(self.v[x])) self.pc += 2;
                                }
                            })[NN]();
                        },
                        0xF000() {
                            ({
                                //FX07
                                //Vx = delayTimer
                                0x0007() {
                                    self.v[x] = self.delayTimer;
                                },
                                //FX0A
                                //Vx =input_key
                                0x000A() {
                                    self.paused = true;
                                    self.input.onNextKeyPress = function (key) {
                                        self.v[x] = key;
                                        self.paused = false;
                                    }.bind(self);
                                },
                                //FX15
                                //delayTimer=Vx                            
                                0x0015() {
                                    self.delayTimer = self.v[x];
                                },
                                //FX18
                                //soundTimer=Vx
                                0x0018() {
                                    self.soundTimer = self.v[x];
                                },
                                //FX1E
                                //I +=Vx
                                0x001E() {
                                    self.i += self.v[x];
                                },
                                //FX29
                                //I=sprite_addr[Vx],一般用4x5字体表示
                                0x0029() {
                                    self.i = self.v[x] * 5;
                                },
                                //FX33
                                //reg_dump(Vx,&I)	
                                0x0033() {
                                    self.memory[self.i] = parseInt(self.v[x] / 100);//取得十进制百位
                                    self.memory[self.i + 1] = parseInt(self.v[x] % 100 / 10);//取得十进制十位
                                    self.memory[self.i + 2] = self.v[x] % 10;//取得十进制个位
                                },
                                //FX55
                                //reg_load(Vx,&I)
                                0x0055() {
                                    for (var i = 0; i <= x; i++) {
                                        self.memory[self.i + i] = self.v[i];
                                    }
                                },
                                //FX65
                                //I +=Vx
                                0x0065() {
                                    for (var i = 0; i <= x; i++) {
                                        self.v[i] = self.memory[self.i + i];
                                    }
                                }
                            })[NN]();
                        }
                    })[opcode & 0xF000]();
                }
            };
            function Screen() {
                this.rows = 32;//32行
                this.columns = 64;//64列
                this.resolution = this.rows * this.columns;
                this.bitMap = new Array(this.resolution);//像素点阵
                this.clear = function () {
                    this.bitMap = new Array(this.resolution);
                }
                this.render = function () { };//显示渲染
                this.setPixel = function (x, y) {
                    // 显示溢出处理
                    if (x > this.columns - 1) while (x > this.columns - 1) x -= this.columns;
                    if (x < 0) while (x < 0) x += this.columns;

                    if (y > this.rows - 1) while (y > this.rows - 1) y -= this.rows;
                    if (y < 0) while (y < 0) y += this.rows;

                    //获取点阵索引
                    var location = x + (y * this.columns);
                    //反向显示,假设二值颜色黑白分别用1、0代表,那么值为1那么就将值设置成0,同理0的话变成1
                    this.bitMap[location] = this.bitMap[location] ^ 1;

                    return !this.bitMap[location];
                }
            };
            function Speaker() {
                var contextClass = (window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.oAudioContext || window.msAudioContext)
                    , context
                    , oscillator
                    , gain;

                if (contextClass) {
                    context = new contextClass();
                    gain = context.createGain();
                    gain.connect(context.destination);
                }

                //播放声音
                this.play = function (frequency) {
                    //API https://developer.mozilla.org/en-US/docs/Web/API/OscillatorNode
                    //示例 https://mdn.github.io/violent-theremin/
                    if (context && !oscillator) {
                        oscillator = context.createOscillator();
                        oscillator.frequency.value = frequency || 440;//声音频率 
                        oscillator.type = oscillator.TRIANGLE;//波形这里用的是三角波 查看示例：https://codepen.io/gregh/pen/LxJEaj
                        oscillator.connect(gain);
                        oscillator.start(0);
                    }
                }

                //停止播放
                this.clear = this.stop = function () {
                    if (oscillator) {
                        oscillator.stop(0);
                        oscillator.disconnect(0);
                        oscillator = null;
                    }
                }
            };
            function Keyboard() {
                var keysPressed = [];//记录按下的按键

                //处理下一个按键
                this.onNextKeyPress = function () { }

                //清空
                this.clear = function () {
                    keysPressed = [];
                    this.onNextKeyPress = function () { }
                }

                //当前按键是否按下
                this.isKeyPressed = function (property) {
                    var key = Keyboard.MAPPING[property];
                    return !!keysPressed[key];
                }

                var self = this;
                this.keyDown = function (event) {
                    var key = String.fromCharCode(event.which);
                    keysPressed[key] = true;

                    for (var property in Keyboard.MAPPING) {
                        var keyCode = Keyboard.MAPPING[property];
                        if (keyCode == key) {
                            try {
                                self.onNextKeyPress(parseInt(property), keyCode);
                            } finally {
                                self.onNextKeyPress = function () { }
                            }
                        }
                    }
                }

                this.keyUp = function (event) {
                    var key = String.fromCharCode(event.which);
                    keysPressed[key] = false;
                }

                window.addEventListener("keydown", this.keyDown, false);//绑定键盘按下时间
                window.addEventListener("keyup", this.keyUp, false);//绑定键盘弹起时间
            };
            //自定义键盘按键对应Chip8输入值
            Keyboard.MAPPING = {
                0x1: "1",
                0x2: "2",
                0x3: "3",
                0xC: "4",
                0x4: "Q",
                0x5: "W",
                0x6: "E",
                0xD: "R",
                0x7: "A",
                0x8: "S",
                0x9: "D",
                0xE: "F",
                0xA: "Z",
                0x0: "X",
                0xB: "C",
                0xF: "V"
            };
            window.CHIP8 = function () {
                var c8 = new CPU();
                c8.screen = new Screen();
                c8.speaker = new Speaker();
                c8.input = new Keyboard();
                return c8;
            };
        })();
    </script>
</head>

<body>
    <input type="file" id="f" />
    <div id="boxs" style="overflow: auto;width:320px;border: 1px solid #666;"></div> 
    <script> 

        var chip8 = CHIP8();
        chip8.screen.render = function () {//自定义实现显示渲染
            var boxs = document.getElementById("boxs");
            boxs.innerHTML = "";
            for (var i of this.bitMap) {
                var d = document.createElement("span");
                d.style = "width: 5px;height: 5px;float: left;";
                d.style.backgroundColor = i ? "#000" : "#fff";
                boxs.appendChild(d);
            }
        };
        f.onchange = function () {
            var fl = this.files[0]
            var reader = new FileReader();
            reader.onload = function (e) {
                chip8.reset();//重置
                chip8.rom = new Uint8Array(e.target.result);//读取到rom
                chip8.loadProgram(chip8.rom);//加载程序
                //启动
                if (chip8._loop) {
                    clearInterval(chip8._loop);
                    chip8._loop = null;
                }
                chip8._loop = setInterval(() => {
                    chip8.cycle();
                }, 1000 / 60);//模拟频率 60
            }
            reader.onerror = function (error) {
            }
            reader.readAsArrayBuffer(fl);
        };
    </script>
</body>

</html>